/*
 * Copyright 2021 Google LLC.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/sksl/SkSLParser.h"

#include "include/core/SkSpan.h"
#include "include/private/base/SkTArray.h"
#include "include/sksl/SkSLVersion.h"
#include "src/base/SkEnumBitMask.h"
#include "src/base/SkNoDestructor.h"
#include "src/core/SkTHash.h"
#include "src/sksl/SkSLBuiltinTypes.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLConstantFolder.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLErrorReporter.h"
#include "src/sksl/SkSLOperator.h"
#include "src/sksl/SkSLString.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLBreakStatement.h"
#include "src/sksl/ir/SkSLContinueStatement.h"
#include "src/sksl/ir/SkSLDiscardStatement.h"
#include "src/sksl/ir/SkSLDoStatement.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLExpressionStatement.h"
#include "src/sksl/ir/SkSLExtension.h"
#include "src/sksl/ir/SkSLFieldAccess.h"
#include "src/sksl/ir/SkSLForStatement.h"
#include "src/sksl/ir/SkSLFunctionCall.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLFunctionDefinition.h"
#include "src/sksl/ir/SkSLFunctionPrototype.h"
#include "src/sksl/ir/SkSLIfStatement.h"
#include "src/sksl/ir/SkSLIndexExpression.h"
#include "src/sksl/ir/SkSLInterfaceBlock.h"
#include "src/sksl/ir/SkSLLayout.h"
#include "src/sksl/ir/SkSLLiteral.h"
#include "src/sksl/ir/SkSLModifierFlags.h"
#include "src/sksl/ir/SkSLModifiersDeclaration.h"
#include "src/sksl/ir/SkSLNop.h"
#include "src/sksl/ir/SkSLPoison.h"
#include "src/sksl/ir/SkSLPostfixExpression.h"
#include "src/sksl/ir/SkSLPrefixExpression.h"
#include "src/sksl/ir/SkSLProgram.h"
#include "src/sksl/ir/SkSLProgramElement.h"
#include "src/sksl/ir/SkSLReturnStatement.h"
#include "src/sksl/ir/SkSLStatement.h"
#include "src/sksl/ir/SkSLStructDefinition.h"
#include "src/sksl/ir/SkSLSwitchStatement.h"
#include "src/sksl/ir/SkSLSwizzle.h"
#include "src/sksl/ir/SkSLSymbol.h"
#include "src/sksl/ir/SkSLSymbolTable.h"
#include "src/sksl/ir/SkSLTernaryExpression.h"
#include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLTypeReference.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"

#include <algorithm>
#include <climits>
#include <initializer_list>
#include <memory>
#include <utility>
#include <vector>

using namespace skia_private;

namespace SkSL {
static constexpr int kMaxParseDepth = 50;

static ModifierFlags parse_modifier_token(Token::Kind token)
{
    switch (token) {
        case Token::Kind::TK_UNIFORM:
            return ModifierFlag::kUniform;
        case Token::Kind::TK_CONST:
            return ModifierFlag::kConst;
        case Token::Kind::TK_IN:
            return ModifierFlag::kIn;
        case Token::Kind::TK_OUT:
            return ModifierFlag::kOut;
        case Token::Kind::TK_INOUT:
            return ModifierFlag::kIn | ModifierFlag::kOut;
        case Token::Kind::TK_FLAT:
            return ModifierFlag::kFlat;
        case Token::Kind::TK_NOPERSPECTIVE:
            return ModifierFlag::kNoPerspective;
        case Token::Kind::TK_PURE:
            return ModifierFlag::kPure;
        case Token::Kind::TK_INLINE:
            return ModifierFlag::kInline;
        case Token::Kind::TK_NOINLINE:
            return ModifierFlag::kNoInline;
        case Token::Kind::TK_HIGHP:
            return ModifierFlag::kHighp;
        case Token::Kind::TK_MEDIUMP:
            return ModifierFlag::kMediump;
        case Token::Kind::TK_LOWP:
            return ModifierFlag::kLowp;
        case Token::Kind::TK_EXPORT:
            return ModifierFlag::kExport;
        case Token::Kind::TK_ES3:
            return ModifierFlag::kES3;
        case Token::Kind::TK_WORKGROUP:
            return ModifierFlag::kWorkgroup;
        case Token::Kind::TK_READONLY:
            return ModifierFlag::kReadOnly;
        case Token::Kind::TK_WRITEONLY:
            return ModifierFlag::kWriteOnly;
        case Token::Kind::TK_BUFFER:
            return ModifierFlag::kBuffer;
        case Token::Kind::TK_PIXELLOCAL:
            return ModifierFlag::kPixelLocal;
        default:
            return ModifierFlag::kNone;
    }
}

class Parser::AutoDepth {
public:
    AutoDepth(Parser *p) : fParser(p), fDepth(0) {}

    ~AutoDepth()
    {
        fParser->fDepth -= fDepth;
    }

    bool increase()
    {
        ++fDepth;
        ++fParser->fDepth;
        if (fParser->fDepth > kMaxParseDepth) {
            fParser->error(fParser->peek(), "exceeded max parse depth");
            fParser->fEncounteredFatalError = true;
            return false;
        }
        return true;
    }

private:
    Parser *fParser;
    int fDepth;
};

class Parser::AutoSymbolTable {
public:
    AutoSymbolTable(Parser *p, std::unique_ptr<SymbolTable> *newSymbolTable, bool enable = true)
    {
        if (enable) {
            fParser = p;
            SymbolTable *&ctxSymbols = this->contextSymbolTable();
            *newSymbolTable = std::make_unique<SymbolTable>(ctxSymbols, ctxSymbols->isBuiltin());
            ctxSymbols = newSymbolTable->get();
        }
    }

    ~AutoSymbolTable()
    {
        if (fParser) {
            SymbolTable *&ctxSymbols = this->contextSymbolTable();
            ctxSymbols = ctxSymbols->fParent;
        }
    }

private:
    SymbolTable *&contextSymbolTable()
    {
        return fParser->fCompiler.context().fSymbolTable;
    }

    Parser *fParser = nullptr;
};

class Parser::Checkpoint {
public:
    Checkpoint(Parser *p) : fParser(p)
    {
        Context &context = fParser->fCompiler.context();
        fPushbackCheckpoint = fParser->fPushback;
        fLexerCheckpoint = fParser->fLexer.getCheckpoint();
        fOldErrorReporter = context.fErrors;
        fOldEncounteredFatalError = fParser->fEncounteredFatalError;
        SkASSERT(fOldErrorReporter);
        context.setErrorReporter(&fErrorReporter);
    }

    ~Checkpoint()
    {
        SkASSERTF(!fOldErrorReporter, "Checkpoint was not accepted or rewound before destruction");
    }

    void accept()
    {
        this->restoreErrorReporter();
        // Parser errors should have been fatal, but we can encounter other errors like type
        // mismatches despite accepting the parse. Forward those messages to the actual error
        // handler now.
        fErrorReporter.forwardErrors(fParser);
    }

    void rewind()
    {
        this->restoreErrorReporter();
        fParser->fPushback = fPushbackCheckpoint;
        fParser->fLexer.rewindToCheckpoint(fLexerCheckpoint);
        fParser->fEncounteredFatalError = fOldEncounteredFatalError;
    }

private:
    class ForwardingErrorReporter : public ErrorReporter {
    public:
        void handleError(std::string_view msg, Position pos) override
        {
            fErrors.push_back({ std::string(msg), pos });
        }

        void forwardErrors(Parser *parser)
        {
            for (const Error &error : fErrors) {
                parser->error(error.fPos, error.fMsg);
            }
        }

    private:
        struct Error {
            std::string fMsg;
            Position fPos;
        };

        skia_private::TArray<Error> fErrors;
    };

    void restoreErrorReporter()
    {
        SkASSERT(fOldErrorReporter);
        fParser->fCompiler.context().setErrorReporter(fOldErrorReporter);
        fOldErrorReporter = nullptr;
    }

    Parser *fParser;
    Token fPushbackCheckpoint;
    SkSL::Lexer::Checkpoint fLexerCheckpoint;
    ForwardingErrorReporter fErrorReporter;
    ErrorReporter *fOldErrorReporter;
    bool fOldEncounteredFatalError;
};

Parser::Parser(Compiler *compiler, const ProgramSettings &settings, ProgramKind kind, std::unique_ptr<std::string> text)
    : fCompiler(*compiler),
      fSettings(settings),
      fKind(kind),
      fText(std::move(text)),
      fPushback(Token::Kind::TK_NONE, /* offset= */ -1, /* length= */ -1)
{
    fLexer.start(*fText);
}

Parser::~Parser() = default;

SymbolTable *Parser::symbolTable()
{
    return fCompiler.symbolTable();
}

Token Parser::nextRawToken()
{
    Token token;
    if (fPushback.fKind != Token::Kind::TK_NONE) {
        // Retrieve the token from the pushback buffer.
        token = fPushback;
        fPushback.fKind = Token::Kind::TK_NONE;
    } else {
        // Fetch a token from the lexer.
        token = fLexer.next();

        // Some tokens are always invalid, so we detect and report them here.
        switch (token.fKind) {
            case Token::Kind::TK_PRIVATE_IDENTIFIER:
                if (ProgramConfig::AllowsPrivateIdentifiers(fKind)) {
                    token.fKind = Token::Kind::TK_IDENTIFIER;
                    break;
                }
                [[fallthrough]];

            case Token::Kind::TK_RESERVED:
                this->error(token, "name '" + std::string(this->text(token)) + "' is reserved");
                token.fKind = Token::Kind::TK_IDENTIFIER; // reduces additional follow-up errors
                break;

            case Token::Kind::TK_BAD_OCTAL:
                this->error(token, "'" + std::string(this->text(token)) + "' is not a valid octal number");
                break;

            default:
                break;
        }
    }

    return token;
}

static bool is_whitespace(Token::Kind kind)
{
    switch (kind) {
        case Token::Kind::TK_WHITESPACE:
        case Token::Kind::TK_LINE_COMMENT:
        case Token::Kind::TK_BLOCK_COMMENT:
            return true;

        default:
            return false;
    }
}

bool Parser::expectNewline()
{
    Token token = this->nextRawToken();
    if (token.fKind == Token::Kind::TK_WHITESPACE) {
        // The lexer doesn't distinguish newlines from other forms of whitespace, so we check
        // for newlines by searching through the token text.
        std::string_view tokenText = this->text(token);
        if (tokenText.find_first_of('\r') != std::string_view::npos ||
            tokenText.find_first_of('\n') != std::string_view::npos) {
            return true;
        }
    }
    // We didn't find a newline.
    this->pushback(token);
    return false;
}

Token Parser::nextToken()
{
    for (;;) {
        Token token = this->nextRawToken();
        if (!is_whitespace(token.fKind)) {
            return token;
        }
    }
}

void Parser::pushback(Token t)
{
    SkASSERT(fPushback.fKind == Token::Kind::TK_NONE);
    fPushback = t;
}

Token Parser::peek()
{
    if (fPushback.fKind == Token::Kind::TK_NONE) {
        fPushback = this->nextToken();
    }
    return fPushback;
}

bool Parser::checkNext(Token::Kind kind, Token *result)
{
    if (fPushback.fKind != Token::Kind::TK_NONE && fPushback.fKind != kind) {
        return false;
    }
    Token next = this->nextToken();
    if (next.fKind == kind) {
        if (result) {
            *result = next;
        }
        return true;
    }
    this->pushback(next);
    return false;
}

bool Parser::expect(Token::Kind kind, const char *expected, Token *result)
{
    Token next = this->nextToken();
    if (next.fKind == kind) {
        if (result) {
            *result = next;
        }
        return true;
    } else {
        this->error(next, "expected " + std::string(expected) + ", but found '" + std::string(this->text(next)) + "'");
        this->fEncounteredFatalError = true;
        return false;
    }
}

bool Parser::expectIdentifier(Token *result)
{
    if (!this->expect(Token::Kind::TK_IDENTIFIER, "an identifier", result)) {
        return false;
    }
    if (this->symbolTable()->isBuiltinType(this->text(*result))) {
        this->error(*result, "expected an identifier, but found type '" + std::string(this->text(*result)) + "'");
        this->fEncounteredFatalError = true;
        return false;
    }
    return true;
}

bool Parser::checkIdentifier(Token *result)
{
    if (!this->checkNext(Token::Kind::TK_IDENTIFIER, result)) {
        return false;
    }
    if (this->symbolTable()->isBuiltinType(this->text(*result))) {
        this->pushback(*result);
        return false;
    }
    return true;
}

std::string_view Parser::text(Token token)
{
    return std::string_view(fText->data() + token.fOffset, token.fLength);
}

Position Parser::position(Token t)
{
    if (t.fOffset >= 0) {
        return Position::Range(t.fOffset, t.fOffset + t.fLength);
    } else {
        return Position();
    }
}

void Parser::error(Token token, std::string_view msg)
{
    this->error(this->position(token), msg);
}

void Parser::error(Position position, std::string_view msg)
{
    fCompiler.context().fErrors->error(position, msg);
}

Position Parser::rangeFrom(Position start)
{
    int offset = fPushback.fKind != Token::Kind::TK_NONE ? fPushback.fOffset : fLexer.getCheckpoint().fOffset;
    return Position::Range(start.startOffset(), offset);
}

Position Parser::rangeFrom(Token start)
{
    return this->rangeFrom(this->position(start));
}

/* declaration* END_OF_FILE */
std::unique_ptr<Program> Parser::programInheritingFrom(const SkSL::Module *module)
{
    this->declarations();
    std::unique_ptr<Program> result;
    if (fCompiler.errorReporter().errorCount() == 0) {
        result = fCompiler.releaseProgram(std::move(fText), std::move(fProgramElements));
    } else {
        fProgramElements.clear();
    }
    return result;
}

std::unique_ptr<SkSL::Module> Parser::moduleInheritingFrom(const SkSL::Module *parentModule)
{
    this->declarations();
    this->symbolTable()->takeOwnershipOfString(std::move(*fText));
    auto result = std::make_unique<SkSL::Module>();
    result->fParent = parentModule;
    result->fSymbols = std::move(fCompiler.fGlobalSymbols);
    result->fElements = std::move(fProgramElements);
    return result;
}

void Parser::declarations()
{
    fEncounteredFatalError = false;

    // If the program is 8MB or longer (Position::kMaxOffset), error reporting goes off the rails.
    // At any rate, there's no good reason for a program to be this long.
    if (fText->size() >= Position::kMaxOffset) {
        this->error(Position(), "program is too large");
        return;
    }

    // Any #version directive must appear as the first thing in a file
    if (this->peek().fKind == Token::Kind::TK_DIRECTIVE) {
        this->directive(/* allowVersion= */ true);
    }

    while (!fEncounteredFatalError) {
        // We should always be at global scope when processing top-level declarations.
        SkASSERT(fCompiler.context().fSymbolTable == fCompiler.globalSymbols());

        switch (this->peek().fKind) {
            case Token::Kind::TK_END_OF_FILE:
                return;

            case Token::Kind::TK_INVALID:
                this->error(this->peek(), "invalid token");
                return;

            case Token::Kind::TK_DIRECTIVE:
                this->directive(/* allowVersion= */ false);
                break;

            default:
                this->declaration();
                break;
        }
    }
}

/* DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER NEWLINE */
void Parser::extensionDirective(Position start)
{
    Token name;
    if (!this->expectIdentifier(&name)) {
        return;
    }
    if (!this->expect(Token::Kind::TK_COLON, "':'")) {
        return;
    }
    Token behavior;
    if (!this->expect(Token::Kind::TK_IDENTIFIER, "an identifier", &behavior)) {
        return;
    }
    // We expect a newline immediately after `#extension name : behavior`.
    if (this->expectNewline()) {
        std::unique_ptr<SkSL::Extension> ext =
            Extension::Convert(fCompiler.context(), this->rangeFrom(start), this->text(name), this->text(behavior));
        if (ext) {
            fProgramElements.push_back(std::move(ext));
        }
    } else {
        this->error(start, "invalid #extension directive");
    }
}

/* DIRECTIVE(#version) INTLITERAL NEWLINE */
void Parser::versionDirective(Position start, bool allowVersion)
{
    if (!allowVersion) {
        this->error(start, "#version directive must appear before anything else");
        return;
    }
    SKSL_INT version;
    if (!this->intLiteral(&version)) {
        return;
    }
    switch (version) {
        case 100:
            fCompiler.context().fConfig->fRequiredSkSLVersion = Version::k100;
            break;
        case 300:
            fCompiler.context().fConfig->fRequiredSkSLVersion = Version::k300;
            break;
        default:
            this->error(start, "unsupported version number");
            return;
    }
    // We expect a newline after a #version directive.
    if (!this->expectNewline()) {
        this->error(start, "invalid #version directive");
    }
}

/* DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER NEWLINE |
   DIRECTIVE(#version) INTLITERAL NEWLINE */
void Parser::directive(bool allowVersion)
{
    Token start;
    if (!this->expect(Token::Kind::TK_DIRECTIVE, "a directive", &start)) {
        return;
    }
    std::string_view text = this->text(start);
    if (text == "#extension") {
        return this->extensionDirective(this->position(start));
    }
    if (text == "#version") {
        return this->versionDirective(this->position(start), allowVersion);
    }
    this->error(start, "unsupported directive '" + std::string(this->text(start)) + "'");
}

bool Parser::modifiersDeclarationEnd(const SkSL::Modifiers &mods)
{
    std::unique_ptr<ModifiersDeclaration> decl = ModifiersDeclaration::Convert(fCompiler.context(), mods);
    if (!decl) {
        return false;
    }
    fProgramElements.push_back(std::move(decl));
    return true;
}

/* modifiers (structVarDeclaration | type IDENTIFIER ((LPAREN parameter (COMMA parameter)* RPAREN
   (block | SEMICOLON)) | SEMICOLON) | interfaceBlock) */
bool Parser::declaration()
{
    Token start = this->peek();
    if (start.fKind == Token::Kind::TK_SEMICOLON) {
        this->nextToken();
        this->error(start, "expected a declaration, but found ';'");
        return false;
    }
    Modifiers modifiers = this->modifiers();
    Token lookahead = this->peek();
    if (lookahead.fKind == Token::Kind::TK_IDENTIFIER && !this->symbolTable()->isType(this->text(lookahead))) {
        // we have an identifier that's not a type, could be the start of an interface block
        return this->interfaceBlock(modifiers);
    }
    if (lookahead.fKind == Token::Kind::TK_SEMICOLON) {
        this->nextToken();
        return this->modifiersDeclarationEnd(modifiers);
    }
    if (lookahead.fKind == Token::Kind::TK_STRUCT) {
        this->structVarDeclaration(this->position(start), modifiers);
        return true;
    }
    const Type *type = this->type(&modifiers);
    if (!type) {
        return false;
    }
    Token name;
    if (!this->expectIdentifier(&name)) {
        return false;
    }
    if (this->checkNext(Token::Kind::TK_LPAREN)) {
        return this->functionDeclarationEnd(this->position(start), modifiers, type, name);
    } else {
        this->globalVarDeclarationEnd(this->position(start), modifiers, type, name);
        return true;
    }
}

/* (RPAREN | VOID RPAREN | parameter (COMMA parameter)* RPAREN) (block | SEMICOLON) */
bool Parser::functionDeclarationEnd(Position start, Modifiers &modifiers, const Type *returnType, const Token &name)
{
    Token lookahead = this->peek();
    bool validParams = true;
    STArray<8, std::unique_ptr<Variable>> parameters;
    if (lookahead.fKind == Token::Kind::TK_RPAREN) {
        // `()` means no parameters at all.
    } else if (lookahead.fKind == Token::Kind::TK_IDENTIFIER && this->text(lookahead) == "void") {
        // `(void)` also means no parameters at all.
        this->nextToken();
    } else {
        for (;;) {
            std::unique_ptr<SkSL::Variable> param;
            if (!this->parameter(&param)) {
                return false;
            }
            validParams = validParams && param;
            parameters.push_back(std::move(param));
            if (!this->checkNext(Token::Kind::TK_COMMA)) {
                break;
            }
        }
    }
    if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
        return false;
    }

    SkSL::FunctionDeclaration *decl = nullptr;
    if (validParams) {
        decl = SkSL::FunctionDeclaration::Convert(fCompiler.context(), this->rangeFrom(start), modifiers,
            this->text(name), std::move(parameters), start, returnType);
    }

    if (this->checkNext(Token::Kind::TK_SEMICOLON)) {
        return this->prototypeFunction(decl);
    } else {
        return this->defineFunction(decl);
    }
}

bool Parser::prototypeFunction(SkSL::FunctionDeclaration *decl)
{
    if (!decl) {
        return false;
    }
    fProgramElements.push_back(
        std::make_unique<SkSL::FunctionPrototype>(decl->fPosition, decl, fCompiler.context().fConfig->fIsBuiltinCode));
    return true;
}

bool Parser::defineFunction(SkSL::FunctionDeclaration *decl)
{
    const Context &context = fCompiler.context();
    Token bodyStart = this->peek();

    std::unique_ptr<SymbolTable> symbolTable;
    std::unique_ptr<Statement> body;
    {
        // Create a symbol table for the function which includes the parameters.
        AutoSymbolTable symbols(this, &symbolTable);
        if (decl) {
            for (Variable *param : decl->parameters()) {
                symbolTable->addWithoutOwnership(fCompiler.context(), param);
            }
        }

        // Parse the function body.
        body = this->block(/* introduceNewScope= */ false, /* adoptExistingSymbolTable= */ &symbolTable);
    }

    // If there was a problem with the declarations or body, don't actually create a definition.
    if (!decl || !body) {
        return false;
    }

    std::unique_ptr<SkSL::Statement> block = std::move(body);
    SkASSERT(block->is<Block>());
    Position pos = this->rangeFrom(bodyStart);
    block->fPosition = pos;

    std::unique_ptr<FunctionDefinition> function = FunctionDefinition::Convert(context, pos, *decl, std::move(block),
        /* builtin= */ false);
    if (!function) {
        return false;
    }
    decl->setDefinition(function.get());
    fProgramElements.push_back(std::move(function));
    return true;
}

bool Parser::arraySize(SKSL_INT *outResult)
{
    // Start out with a safe value that won't generate any errors downstream
    *outResult = 1;
    Token next = this->peek();
    if (next.fKind == Token::Kind::TK_RBRACKET) {
        this->error(this->position(next), "unsized arrays are not permitted here");
        return true;
    }
    std::unique_ptr<Expression> sizeLiteral = this->expression();
    if (!sizeLiteral) {
        return false;
    }
    if (!sizeLiteral->is<Poison>()) {
        SKSL_INT size;
        if (!ConstantFolder::GetConstantInt(*sizeLiteral, &size)) {
            this->error(sizeLiteral->fPosition, "array size must be an integer");
            return true;
        }
        if (size > INT32_MAX) {
            this->error(sizeLiteral->fPosition, "array size out of bounds");
            return true;
        }
        if (size <= 0) {
            this->error(sizeLiteral->fPosition, "array size must be positive");
            return true;
        }
        // Now that we've validated it, output the real value
        *outResult = size;
    }
    return true;
}

const Type *Parser::arrayType(const Type *base, int count, Position pos)
{
    const Context &context = fCompiler.context();
    count = base->convertArraySize(context, pos, pos, count);
    if (!count) {
        return context.fTypes.fPoison.get();
    }
    return this->symbolTable()->addArrayDimension(base, count);
}

const Type *Parser::unsizedArrayType(const Type *base, Position pos)
{
    const Context &context = fCompiler.context();
    if (!base->checkIfUsableInArray(context, pos)) {
        return context.fTypes.fPoison.get();
    }
    return this->symbolTable()->addArrayDimension(base, SkSL::Type::kUnsizedArray);
}

bool Parser::parseArrayDimensions(Position pos, const Type **type)
{
    Token next;
    while (this->checkNext(Token::Kind::TK_LBRACKET, &next)) {
        if (this->checkNext(Token::Kind::TK_RBRACKET)) {
            if (this->allowUnsizedArrays()) {
                *type = this->unsizedArrayType(*type, this->rangeFrom(pos));
            } else {
                this->error(this->rangeFrom(pos), "unsized arrays are not permitted here");
            }
        } else {
            SKSL_INT size;
            if (!this->arraySize(&size)) {
                return false;
            }
            if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) {
                return false;
            }
            *type = this->arrayType(*type, size, this->rangeFrom(pos));
        }
    }
    return true;
}

bool Parser::parseInitializer(Position pos, std::unique_ptr<Expression> *initializer)
{
    if (this->checkNext(Token::Kind::TK_EQ)) {
        *initializer = this->assignmentExpression();
        return *initializer != nullptr;
    }
    return true;
}

void Parser::addGlobalVarDeclaration(std::unique_ptr<VarDeclaration> decl)
{
    if (decl) {
        fProgramElements.push_back(std::make_unique<SkSL::GlobalVarDeclaration>(std::move(decl)));
    }
}

/* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER
   (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */
void Parser::globalVarDeclarationEnd(Position pos, const Modifiers &mods, const Type *baseType, Token name)
{
    const Type *type = baseType;
    std::unique_ptr<Expression> initializer;
    if (!this->parseArrayDimensions(pos, &type)) {
        return;
    }
    if (!this->parseInitializer(pos, &initializer)) {
        return;
    }
    this->addGlobalVarDeclaration(VarDeclaration::Convert(fCompiler.context(), this->rangeFrom(pos), mods, *type,
        this->position(name), this->text(name), VariableStorage::kGlobal, std::move(initializer)));
    while (this->checkNext(Token::Kind::TK_COMMA)) {
        type = baseType;
        Token identifierName;
        if (!this->expectIdentifier(&identifierName)) {
            return;
        }
        if (!this->parseArrayDimensions(pos, &type)) {
            return;
        }
        std::unique_ptr<Expression> anotherInitializer;
        if (!this->parseInitializer(pos, &anotherInitializer)) {
            return;
        }
        this->addGlobalVarDeclaration(VarDeclaration::Convert(fCompiler.context(), this->rangeFrom(identifierName),
            mods, *type, this->position(identifierName), this->text(identifierName), VariableStorage::kGlobal,
            std::move(anotherInitializer)));
    }
    this->expect(Token::Kind::TK_SEMICOLON, "';'");
}

/* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER
   (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */
std::unique_ptr<Statement> Parser::localVarDeclarationEnd(Position pos, const Modifiers &mods, const Type *baseType,
    Token name)
{
    const Type *type = baseType;
    std::unique_ptr<Expression> initializer;
    if (!this->parseArrayDimensions(pos, &type)) {
        return nullptr;
    }
    if (!this->parseInitializer(pos, &initializer)) {
        return nullptr;
    }
    std::unique_ptr<Statement> result = VarDeclaration::Convert(fCompiler.context(), this->rangeFrom(pos), mods, *type,
        this->position(name), this->text(name), VariableStorage::kLocal, std::move(initializer));
    for (;;) {
        if (!this->checkNext(Token::Kind::TK_COMMA)) {
            this->expect(Token::Kind::TK_SEMICOLON, "';'");
            break;
        }
        type = baseType;
        Token identifierName;
        if (!this->expectIdentifier(&identifierName)) {
            break;
        }
        if (!this->parseArrayDimensions(pos, &type)) {
            break;
        }
        std::unique_ptr<Expression> anotherInitializer;
        if (!this->parseInitializer(pos, &anotherInitializer)) {
            break;
        }
        std::unique_ptr<Statement> next = VarDeclaration::Convert(fCompiler.context(), this->rangeFrom(identifierName),
            mods, *type, this->position(identifierName), this->text(identifierName), VariableStorage::kLocal,
            std::move(anotherInitializer));

        result = Block::MakeCompoundStatement(std::move(result), std::move(next));
    }
    pos = this->rangeFrom(pos);
    return this->statementOrNop(pos, std::move(result));
}

/* (varDeclarations | expressionStatement) */
std::unique_ptr<Statement> Parser::varDeclarationsOrExpressionStatement()
{
    Token nextToken = this->peek();
    if (nextToken.fKind == Token::Kind::TK_CONST) {
        // Statements that begin with `const` might be variable declarations, but can't be legal
        // SkSL expression-statements. (SkSL constructors don't take a `const` modifier.)
        return this->varDeclarations();
    }

    if (nextToken.fKind == Token::Kind::TK_HIGHP || nextToken.fKind == Token::Kind::TK_MEDIUMP ||
        nextToken.fKind == Token::Kind::TK_LOWP || this->symbolTable()->isType(this->text(nextToken))) {
        // Statements that begin with a typename are most often variable declarations, but
        // occasionally the type is part of a constructor, and these are actually expression-
        // statements in disguise. First, attempt the common case: parse it as a vardecl.
        Checkpoint checkpoint(this);
        VarDeclarationsPrefix prefix;
        if (this->varDeclarationsPrefix(&prefix)) {
            checkpoint.accept();
            return this->localVarDeclarationEnd(prefix.fPosition, prefix.fModifiers, prefix.fType, prefix.fName);
        }

        // If this statement wasn't actually a vardecl after all, rewind and try parsing it as an
        // expression-statement instead.
        checkpoint.rewind();
    }
    return this->expressionStatement();
}

// Helper function for varDeclarations(). If this function succeeds, we assume that the rest of the
// statement is a variable-declaration statement, not an expression-statement.
bool Parser::varDeclarationsPrefix(VarDeclarationsPrefix *prefixData)
{
    prefixData->fPosition = this->position(this->peek());
    prefixData->fModifiers = this->modifiers();
    prefixData->fType = this->type(&prefixData->fModifiers);
    if (!prefixData->fType) {
        return false;
    }
    return this->expectIdentifier(&prefixData->fName);
}

/* modifiers type IDENTIFIER varDeclarationEnd */
std::unique_ptr<Statement> Parser::varDeclarations()
{
    VarDeclarationsPrefix prefix;
    if (!this->varDeclarationsPrefix(&prefix)) {
        return nullptr;
    }
    return this->localVarDeclarationEnd(prefix.fPosition, prefix.fModifiers, prefix.fType, prefix.fName);
}

/* STRUCT IDENTIFIER LBRACE varDeclaration* RBRACE */
const Type *Parser::structDeclaration()
{
    AutoDepth depth(this);
    Position start = this->position(this->peek());
    if (!this->expect(Token::Kind::TK_STRUCT, "'struct'")) {
        return nullptr;
    }
    Token name;
    if (!this->expectIdentifier(&name)) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_LBRACE, "'{'")) {
        return nullptr;
    }
    if (!depth.increase()) {
        return nullptr;
    }
    TArray<SkSL::Field> fields;
    while (!this->checkNext(Token::Kind::TK_RBRACE)) {
        Token fieldStart = this->peek();
        Modifiers modifiers = this->modifiers();
        const Type *type = this->type(&modifiers);
        if (!type) {
            return nullptr;
        }

        do {
            const Type *actualType = type;
            Token memberName;
            if (!this->expectIdentifier(&memberName)) {
                return nullptr;
            }

            while (this->checkNext(Token::Kind::TK_LBRACKET)) {
                SKSL_INT size;
                if (!this->arraySize(&size)) {
                    return nullptr;
                }
                if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) {
                    return nullptr;
                }
                actualType = this->arrayType(actualType, size, this->rangeFrom(this->position(fieldStart)));
            }

            fields.push_back(SkSL::Field(this->rangeFrom(fieldStart), modifiers.fLayout, modifiers.fFlags,
                this->text(memberName), actualType));
        } while (this->checkNext(Token::Kind::TK_COMMA));

        if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
            return nullptr;
        }
    }
    std::unique_ptr<SkSL::StructDefinition> def =
        StructDefinition::Convert(fCompiler.context(), this->rangeFrom(start), this->text(name), std::move(fields));
    if (!def) {
        return nullptr;
    }

    const Type *result = &def->type();
    fProgramElements.push_back(std::move(def));
    return result;
}

/* structDeclaration ((IDENTIFIER varDeclarationEnd) | SEMICOLON) */
void Parser::structVarDeclaration(Position start, const Modifiers &modifiers)
{
    const Type *type = this->structDeclaration();
    if (!type) {
        return;
    }
    Token name;
    if (this->checkIdentifier(&name)) {
        this->globalVarDeclarationEnd(this->rangeFrom(name), modifiers, type, name);
    } else {
        this->expect(Token::Kind::TK_SEMICOLON, "';'");
    }
}

/* modifiers type IDENTIFIER (LBRACKET INT_LITERAL RBRACKET)? */
bool Parser::parameter(std::unique_ptr<SkSL::Variable> *outParam)
{
    Position pos = this->position(this->peek());
    Modifiers modifiers = this->modifiers();
    const Type *type = this->type(&modifiers);
    if (!type) {
        return false;
    }
    Token name;
    std::string_view nameText;
    Position namePos;
    if (this->checkIdentifier(&name)) {
        nameText = this->text(name);
        namePos = this->position(name);
    } else {
        namePos = this->rangeFrom(pos);
    }
    if (!this->parseArrayDimensions(pos, &type)) {
        return false;
    }
    *outParam = SkSL::Variable::Convert(fCompiler.context(), this->rangeFrom(pos), modifiers.fPosition,
        modifiers.fLayout, modifiers.fFlags, type, namePos, nameText, VariableStorage::kParameter);
    return true;
}

/* * EQ INT_LITERAL */
int Parser::layoutInt()
{
    if (!this->expect(Token::Kind::TK_EQ, "'='")) {
        return -1;
    }
    Token resultToken;
    if (!this->expect(Token::Kind::TK_INT_LITERAL, "a non-negative integer", &resultToken)) {
        return -1;
    }
    std::string_view resultFrag = this->text(resultToken);
    SKSL_INT resultValue;
    if (!SkSL::stoi(resultFrag, &resultValue)) {
        this->error(resultToken, "value in layout is too large: " + std::string(resultFrag));
        return -1;
    }
    return resultValue;
}

/* * EQ IDENTIFIER */
std::string_view Parser::layoutIdentifier()
{
    if (!this->expect(Token::Kind::TK_EQ, "'='")) {
        return {};
    }
    Token resultToken;
    if (!this->expectIdentifier(&resultToken)) {
        return {};
    }
    return this->text(resultToken);
}

/* LAYOUT LPAREN IDENTIFIER (EQ INT_LITERAL)? (COMMA IDENTIFIER (EQ INT_LITERAL)?)* RPAREN */
SkSL::Layout Parser::layout()
{
    using LayoutMap = THashMap<std::string_view, SkSL::LayoutFlag>;
    static SkNoDestructor<LayoutMap> sLayoutTokens(LayoutMap{
        { "location", SkSL::LayoutFlag::kLocation },
        { "offset", SkSL::LayoutFlag::kOffset },
        { "binding", SkSL::LayoutFlag::kBinding },
        { "texture", SkSL::LayoutFlag::kTexture },
        { "sampler", SkSL::LayoutFlag::kSampler },
        { "index", SkSL::LayoutFlag::kIndex },
        { "set", SkSL::LayoutFlag::kSet },
        { "builtin", SkSL::LayoutFlag::kBuiltin },
        { "input_attachment_index", SkSL::LayoutFlag::kInputAttachmentIndex },
        { "origin_upper_left", SkSL::LayoutFlag::kOriginUpperLeft },
        { "blend_support_all_equations", SkSL::LayoutFlag::kBlendSupportAllEquations },
        { "push_constant", SkSL::LayoutFlag::kPushConstant },
        { "color", SkSL::LayoutFlag::kColor },
        { "vulkan", SkSL::LayoutFlag::kVulkan },
        { "metal", SkSL::LayoutFlag::kMetal },
        { "webgpu", SkSL::LayoutFlag::kWebGPU },
        { "direct3d", SkSL::LayoutFlag::kDirect3D },
        { "rgba8", SkSL::LayoutFlag::kRGBA8 },
        { "rgba32f", SkSL::LayoutFlag::kRGBA32F },
        { "r32f", SkSL::LayoutFlag::kR32F },
        { "local_size_x", SkSL::LayoutFlag::kLocalSizeX },
        { "local_size_y", SkSL::LayoutFlag::kLocalSizeY },
        { "local_size_z", SkSL::LayoutFlag::kLocalSizeZ },
    });

    Layout result;
    if (this->checkNext(Token::Kind::TK_LAYOUT) && this->expect(Token::Kind::TK_LPAREN, "'('")) {
        for (;;) {
            Token t = this->nextToken();
            std::string_view text = this->text(t);
            SkSL::LayoutFlag *found = sLayoutTokens->find(text);

            if (!found) {
                this->error(t, "'" + std::string(text) + "' is not a valid layout qualifier");
            } else {
                if (result.fFlags & *found) {
                    this->error(t, "layout qualifier '" + std::string(text) + "' appears more than once");
                }

                result.fFlags |= *found;

                switch (*found) {
                    case SkSL::LayoutFlag::kLocation:
                        result.fLocation = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kOffset:
                        result.fOffset = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kBinding:
                        result.fBinding = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kIndex:
                        result.fIndex = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kSet:
                        result.fSet = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kTexture:
                        result.fTexture = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kSampler:
                        result.fSampler = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kBuiltin:
                        result.fBuiltin = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kInputAttachmentIndex:
                        result.fInputAttachmentIndex = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kLocalSizeX:
                        result.fLocalSizeX = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kLocalSizeY:
                        result.fLocalSizeY = this->layoutInt();
                        break;
                    case SkSL::LayoutFlag::kLocalSizeZ:
                        result.fLocalSizeZ = this->layoutInt();
                        break;
                    default:
                        break;
                }
            }
            if (this->checkNext(Token::Kind::TK_RPAREN)) {
                break;
            }
            if (!this->expect(Token::Kind::TK_COMMA, "','")) {
                break;
            }
        }
    }
    return result;
}

/* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | MEDIUMP | HIGHP | FLAT | NOPERSPECTIVE |
            VARYING | INLINE | WORKGROUP | READONLY | WRITEONLY | BUFFER)* */
Modifiers Parser::modifiers()
{
    int start = this->peek().fOffset;
    SkSL::Layout layout = this->layout();
    Token raw = this->nextRawToken();
    int end = raw.fOffset;
    if (!is_whitespace(raw.fKind)) {
        this->pushback(raw);
    }
    ModifierFlags flags = ModifierFlag::kNone;
    for (;;) {
        ModifierFlags tokenFlag = parse_modifier_token(peek().fKind);
        if (tokenFlag == ModifierFlag::kNone) {
            break;
        }
        Token modifier = this->nextToken();
        if (ModifierFlags duplicateFlags = (tokenFlag & flags)) {
            this->error(modifier, "'" + duplicateFlags.description() + "' appears more than once");
        }
        flags |= tokenFlag;
        end = this->position(modifier).endOffset();
    }
    return Modifiers{ Position::Range(start, end), layout, flags };
}

std::unique_ptr<Statement> Parser::statementOrNop(Position pos, std::unique_ptr<Statement> stmt)
{
    if (!stmt) {
        stmt = Nop::Make();
    }
    if (pos.valid() && !stmt->position().valid()) {
        stmt->setPosition(pos);
    }
    return stmt;
}

/* ifStatement | forStatement | doStatement | whileStatement | block | expression */
std::unique_ptr<Statement> Parser::statement(bool bracesIntroduceNewScope)
{
    AutoDepth depth(this);
    if (!depth.increase()) {
        return nullptr;
    }
    switch (this->peek().fKind) {
        case Token::Kind::TK_IF:
            return this->ifStatement();
        case Token::Kind::TK_FOR:
            return this->forStatement();
        case Token::Kind::TK_DO:
            return this->doStatement();
        case Token::Kind::TK_WHILE:
            return this->whileStatement();
        case Token::Kind::TK_SWITCH:
            return this->switchStatement();
        case Token::Kind::TK_RETURN:
            return this->returnStatement();
        case Token::Kind::TK_BREAK:
            return this->breakStatement();
        case Token::Kind::TK_CONTINUE:
            return this->continueStatement();
        case Token::Kind::TK_DISCARD:
            return this->discardStatement();
        case Token::Kind::TK_LBRACE:
            return this->block(bracesIntroduceNewScope, /* adoptExistingSymbolTable= */ nullptr);
        case Token::Kind::TK_SEMICOLON:
            this->nextToken();
            return Nop::Make();
        case Token::Kind::TK_CONST:
            return this->varDeclarations();
        case Token::Kind::TK_HIGHP:
        case Token::Kind::TK_MEDIUMP:
        case Token::Kind::TK_LOWP:
        case Token::Kind::TK_IDENTIFIER:
            return this->varDeclarationsOrExpressionStatement();
        default:
            return this->expressionStatement();
    }
}

const Type *Parser::findType(Position pos, Modifiers *modifiers, std::string_view name)
{
    const Context &context = fCompiler.context();
    const Symbol *symbol = this->symbolTable()->find(name);
    if (!symbol) {
        this->error(pos, "no symbol named '" + std::string(name) + "'");
        return context.fTypes.fPoison.get();
    }
    if (!symbol->is<Type>()) {
        this->error(pos, "symbol '" + std::string(name) + "' is not a type");
        return context.fTypes.fPoison.get();
    }
    const SkSL::Type *type = &symbol->as<Type>();
    if (!context.fConfig->fIsBuiltinCode) {
        if (!TypeReference::VerifyType(context, type, pos)) {
            return context.fTypes.fPoison.get();
        }
    }
    Position qualifierRange = modifiers->fPosition;
    if (qualifierRange.startOffset() == qualifierRange.endOffset()) {
        qualifierRange = this->rangeFrom(qualifierRange);
    }
    return modifiers ? type->applyQualifiers(context, &modifiers->fFlags, qualifierRange) : type;
}

/* IDENTIFIER(type) (LBRACKET intLiteral? RBRACKET)* QUESTION? */
const Type *Parser::type(Modifiers *modifiers)
{
    Token type;
    if (!this->expect(Token::Kind::TK_IDENTIFIER, "a type", &type)) {
        return nullptr;
    }
    if (!this->symbolTable()->isType(this->text(type))) {
        this->error(type, "no type named '" + std::string(this->text(type)) + "'");
        return fCompiler.context().fTypes.fInvalid.get();
    }
    const Type *result = this->findType(this->position(type), modifiers, this->text(type));
    if (result->isInterfaceBlock()) {
        // SkSL puts interface blocks into the symbol table, but they aren't general-purpose types;
        // you can't use them to declare a variable type or a function return type.
        this->error(type, "expected a type, found '" + std::string(this->text(type)) + "'");
        return fCompiler.context().fTypes.fInvalid.get();
    }
    Token bracket;
    while (this->checkNext(Token::Kind::TK_LBRACKET, &bracket)) {
        if (this->checkNext(Token::Kind::TK_RBRACKET)) {
            if (this->allowUnsizedArrays()) {
                result = this->unsizedArrayType(result, this->rangeFrom(type));
            } else {
                this->error(this->rangeFrom(bracket), "unsized arrays are not permitted here");
            }
        } else {
            SKSL_INT size;
            if (!this->arraySize(&size)) {
                return nullptr;
            }
            this->expect(Token::Kind::TK_RBRACKET, "']'");
            result = this->arrayType(result, size, this->rangeFrom(type));
        }
    }
    return result;
}

/* IDENTIFIER LBRACE
     varDeclaration+
   RBRACE (IDENTIFIER (LBRACKET expression RBRACKET)*)? SEMICOLON */
bool Parser::interfaceBlock(const Modifiers &modifiers)
{
    Token typeName;
    if (!this->expectIdentifier(&typeName)) {
        return false;
    }
    if (this->peek().fKind != Token::Kind::TK_LBRACE) {
        // we only get into interfaceBlock if we found a top-level identifier which was not a type.
        // 99% of the time, the user was not actually intending to create an interface block, so
        // it's better to report it as an unknown type
        this->error(typeName, "no type named '" + std::string(this->text(typeName)) + "'");
        return false;
    }
    this->nextToken();
    TArray<SkSL::Field> fields;
    while (!this->checkNext(Token::Kind::TK_RBRACE)) {
        Position fieldPos = this->position(this->peek());
        Modifiers fieldModifiers = this->modifiers();
        const Type *type = this->type(&fieldModifiers);
        if (!type) {
            return false;
        }
        do {
            Token fieldName;
            if (!this->expectIdentifier(&fieldName)) {
                return false;
            }
            const Type *actualType = type;
            if (this->checkNext(Token::Kind::TK_LBRACKET)) {
                Token sizeToken = this->peek();
                if (sizeToken.fKind != Token::Kind::TK_RBRACKET) {
                    SKSL_INT size;
                    if (!this->arraySize(&size)) {
                        return false;
                    }
                    actualType = this->arrayType(actualType, size, this->position(typeName));
                } else if (this->allowUnsizedArrays()) {
                    actualType = this->unsizedArrayType(actualType, this->position(typeName));
                } else {
                    this->error(sizeToken, "unsized arrays are not permitted here");
                }
                this->expect(Token::Kind::TK_RBRACKET, "']'");
            }
            if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
                return false;
            }

            fields.push_back(SkSL::Field(this->rangeFrom(fieldPos), fieldModifiers.fLayout, fieldModifiers.fFlags,
                this->text(fieldName), actualType));
        } while (this->checkNext(Token::Kind::TK_COMMA));
    }
    std::string_view instanceName;
    Token instanceNameToken;
    SKSL_INT size = 0;
    if (this->checkIdentifier(&instanceNameToken)) {
        instanceName = this->text(instanceNameToken);
        if (this->checkNext(Token::Kind::TK_LBRACKET)) {
            if (!this->arraySize(&size)) {
                return false;
            }
            this->expect(Token::Kind::TK_RBRACKET, "']'");
        }
    }
    this->expect(Token::Kind::TK_SEMICOLON, "';'");

    if (std::unique_ptr<SkSL::InterfaceBlock> ib = InterfaceBlock::Convert(fCompiler.context(),
        this->position(typeName), modifiers, this->text(typeName), std::move(fields), instanceName, size)) {
        fProgramElements.push_back(std::move(ib));
        return true;
    }
    return false;
}

/* IF LPAREN expression RPAREN statement (ELSE statement)? */
std::unique_ptr<Statement> Parser::ifStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_IF, "'if'", &start)) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
        return nullptr;
    }
    std::unique_ptr<Expression> test = this->expression();
    if (!test) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
        return nullptr;
    }
    std::unique_ptr<Statement> ifTrue = this->statement();
    if (!ifTrue) {
        return nullptr;
    }
    std::unique_ptr<Statement> ifFalse;
    if (this->checkNext(Token::Kind::TK_ELSE)) {
        ifFalse = this->statement();
        if (!ifFalse) {
            return nullptr;
        }
    }
    Position pos = this->rangeFrom(start);
    return this->statementOrNop(pos,
        IfStatement::Convert(fCompiler.context(), pos, std::move(test), std::move(ifTrue), std::move(ifFalse)));
}

/* DO statement WHILE LPAREN expression RPAREN SEMICOLON */
std::unique_ptr<Statement> Parser::doStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_DO, "'do'", &start)) {
        return nullptr;
    }
    std::unique_ptr<Statement> statement = this->statement();
    if (!statement) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_WHILE, "'while'")) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
        return nullptr;
    }
    std::unique_ptr<Expression> test = this->expression();
    if (!test) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
        return nullptr;
    }
    Position pos = this->rangeFrom(start);
    return this->statementOrNop(pos,
        DoStatement::Convert(fCompiler.context(), pos, std::move(statement), std::move(test)));
}

/* WHILE LPAREN expression RPAREN STATEMENT */
std::unique_ptr<Statement> Parser::whileStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_WHILE, "'while'", &start)) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
        return nullptr;
    }
    std::unique_ptr<Expression> test = this->expression();
    if (!test) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
        return nullptr;
    }
    std::unique_ptr<Statement> statement = this->statement();
    if (!statement) {
        return nullptr;
    }
    Position pos = this->rangeFrom(start);
    return this->statementOrNop(pos,
        ForStatement::ConvertWhile(fCompiler.context(), pos, std::move(test), std::move(statement)));
}

/* COLON statement* */
bool Parser::switchCaseBody(ExpressionArray *values, StatementArray *caseBlocks, std::unique_ptr<Expression> caseValue)
{
    if (!this->expect(Token::Kind::TK_COLON, "':'")) {
        return false;
    }
    StatementArray statements;
    while (this->peek().fKind != Token::Kind::TK_RBRACE && this->peek().fKind != Token::Kind::TK_CASE &&
        this->peek().fKind != Token::Kind::TK_DEFAULT) {
        std::unique_ptr<Statement> s = this->statement();
        if (!s) {
            return false;
        }
        statements.push_back(std::move(s));
    }
    values->push_back(std::move(caseValue));
    caseBlocks->push_back(SkSL::Block::Make(Position(), std::move(statements), Block::Kind::kUnbracedBlock));
    return true;
}

/* CASE expression COLON statement* */
bool Parser::switchCase(ExpressionArray *values, StatementArray *caseBlocks)
{
    Token start;
    if (!this->expect(Token::Kind::TK_CASE, "'case'", &start)) {
        return false;
    }
    std::unique_ptr<Expression> caseValue = this->expression();
    if (!caseValue) {
        return false;
    }
    return this->switchCaseBody(values, caseBlocks, std::move(caseValue));
}

/* SWITCH LPAREN expression RPAREN LBRACE switchCase* (DEFAULT COLON statement*)? RBRACE */
std::unique_ptr<Statement> Parser::switchStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_SWITCH, "'switch'", &start)) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
        return nullptr;
    }
    std::unique_ptr<Expression> value = this->expression();
    if (!value) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_LBRACE, "'{'")) {
        return nullptr;
    }

    std::unique_ptr<SymbolTable> symbolTable;
    ExpressionArray values;
    StatementArray caseBlocks;
    {
        // Keeping a tight scope around AutoSymbolTable is important here. SwitchStatement::Convert
        // may end up creating a new symbol table if the HoistSwitchVarDeclarationsAtTopLevel
        // transform is used. We want ~AutoSymbolTable to happen first, so it can restore the
        // context's active symbol table to the enclosing block instead of the switch's inner block.
        AutoSymbolTable symbols(this, &symbolTable);

        while (this->peek().fKind == Token::Kind::TK_CASE) {
            if (!this->switchCase(&values, &caseBlocks)) {
                return nullptr;
            }
        }
        // Requiring `default:` to be last (in defiance of C and GLSL) was a deliberate decision.
        // Other parts of the compiler are allowed to rely upon this assumption.
        if (this->checkNext(Token::Kind::TK_DEFAULT)) {
            if (!this->switchCaseBody(&values, &caseBlocks, /* value= */ nullptr)) {
                return nullptr;
            }
        }
        if (!this->expect(Token::Kind::TK_RBRACE, "'}'")) {
            return nullptr;
        }
    }

    Position pos = this->rangeFrom(start);
    return this->statementOrNop(pos, SwitchStatement::Convert(fCompiler.context(), pos, std::move(value),
        std::move(values), std::move(caseBlocks), std::move(symbolTable)));
}

static Position range_of_at_least_one_char(int start, int end)
{
    return Position::Range(start, std::max(end, start + 1));
}

/* FOR LPAREN (declaration | expression)? SEMICOLON expression? SEMICOLON expression? RPAREN
   STATEMENT */
std::unique_ptr<Statement> Parser::forStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_FOR, "'for'", &start)) {
        return nullptr;
    }
    Token lparen;
    if (!this->expect(Token::Kind::TK_LPAREN, "'('", &lparen)) {
        return nullptr;
    }
    std::unique_ptr<SymbolTable> symbolTable;
    std::unique_ptr<Statement> initializer;
    std::unique_ptr<Expression> test;
    std::unique_ptr<Expression> next;
    std::unique_ptr<Statement> statement;
    int firstSemicolonOffset;
    Token secondSemicolon;
    Token rparen;
    {
        AutoSymbolTable symbols(this, &symbolTable);

        Token nextToken = this->peek();
        if (nextToken.fKind == Token::Kind::TK_SEMICOLON) {
            // An empty init-statement.
            firstSemicolonOffset = this->nextToken().fOffset;
        } else {
            // The init-statement must be an expression or variable declaration.
            initializer = this->varDeclarationsOrExpressionStatement();
            if (!initializer) {
                return nullptr;
            }
            firstSemicolonOffset = fLexer.getCheckpoint().fOffset - 1;
        }
        if (this->peek().fKind != Token::Kind::TK_SEMICOLON) {
            test = this->expression();
            if (!test) {
                return nullptr;
            }
        }
        if (!this->expect(Token::Kind::TK_SEMICOLON, "';'", &secondSemicolon)) {
            return nullptr;
        }
        if (this->peek().fKind != Token::Kind::TK_RPAREN) {
            next = this->expression();
            if (!next) {
                return nullptr;
            }
        }
        if (!this->expect(Token::Kind::TK_RPAREN, "')'", &rparen)) {
            return nullptr;
        }
        statement = this->statement(/* bracesIntroduceNewScope= */ false);
        if (!statement) {
            return nullptr;
        }
    }
    Position pos = this->rangeFrom(start);
    ForLoopPositions loopPositions{
        range_of_at_least_one_char(lparen.fOffset + 1, firstSemicolonOffset),
        range_of_at_least_one_char(firstSemicolonOffset + 1, secondSemicolon.fOffset),
        range_of_at_least_one_char(secondSemicolon.fOffset + 1, rparen.fOffset),
    };
    return this->statementOrNop(pos, ForStatement::Convert(fCompiler.context(), pos, loopPositions,
        std::move(initializer), std::move(test), std::move(next), std::move(statement), std::move(symbolTable)));
}

/* RETURN expression? SEMICOLON */
std::unique_ptr<Statement> Parser::returnStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_RETURN, "'return'", &start)) {
        return nullptr;
    }
    std::unique_ptr<Expression> expression;
    if (this->peek().fKind != Token::Kind::TK_SEMICOLON) {
        expression = this->expression();
        if (!expression) {
            return nullptr;
        }
    }
    if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
        return nullptr;
    }
    // We do not check for errors, or coerce the value to the correct type, until the return
    // statement is actually added to a function. (This is done in FunctionDefinition::Convert.)
    return ReturnStatement::Make(this->rangeFrom(start), std::move(expression));
}

/* BREAK SEMICOLON */
std::unique_ptr<Statement> Parser::breakStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_BREAK, "'break'", &start)) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
        return nullptr;
    }
    return SkSL::BreakStatement::Make(this->position(start));
}

/* CONTINUE SEMICOLON */
std::unique_ptr<Statement> Parser::continueStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_CONTINUE, "'continue'", &start)) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
        return nullptr;
    }
    return SkSL::ContinueStatement::Make(this->position(start));
}

/* DISCARD SEMICOLON */
std::unique_ptr<Statement> Parser::discardStatement()
{
    Token start;
    if (!this->expect(Token::Kind::TK_DISCARD, "'continue'", &start)) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
        return nullptr;
    }
    Position pos = this->position(start);
    return this->statementOrNop(pos, SkSL::DiscardStatement::Convert(fCompiler.context(), pos));
}

/* LBRACE statement* RBRACE */
std::unique_ptr<Statement> Parser::block(bool introduceNewScope, std::unique_ptr<SymbolTable> *adoptExistingSymbolTable)
{
    // We can't introduce a new scope _and_ adopt an existing symbol table.
    SkASSERT(!(introduceNewScope && adoptExistingSymbolTable));

    AutoDepth depth(this);
    Token start;
    if (!this->expect(Token::Kind::TK_LBRACE, "'{'", &start)) {
        return nullptr;
    }
    if (!depth.increase()) {
        return nullptr;
    }

    std::unique_ptr<SymbolTable> newSymbolTable;
    std::unique_ptr<SymbolTable> *symbolTableToUse =
        adoptExistingSymbolTable ? adoptExistingSymbolTable : &newSymbolTable;

    StatementArray statements;
    {
        AutoSymbolTable symbols(this, symbolTableToUse, /* enable= */ introduceNewScope);

        // Consume statements until we reach the closing brace.
        for (;;) {
            Token::Kind tokenKind = this->peek().fKind;
            if (tokenKind == Token::Kind::TK_RBRACE) {
                this->nextToken();
                break;
            }
            if (tokenKind == Token::Kind::TK_END_OF_FILE) {
                this->error(this->peek(), "expected '}', but found end of file");
                return nullptr;
            }
            if (std::unique_ptr<Statement> statement = this->statement()) {
                statements.push_back(std::move(statement));
            }
            if (fEncounteredFatalError) {
                return nullptr;
            }
        }
    }
    return SkSL::Block::MakeBlock(this->rangeFrom(start), std::move(statements), Block::Kind::kBracedScope,
        std::move(*symbolTableToUse));
}

/* expression SEMICOLON */
std::unique_ptr<Statement> Parser::expressionStatement()
{
    std::unique_ptr<Expression> expr = this->expression();
    if (!expr) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
        return nullptr;
    }
    Position pos = expr->position();
    return this->statementOrNop(pos, SkSL::ExpressionStatement::Convert(fCompiler.context(), std::move(expr)));
}

std::unique_ptr<Expression> Parser::poison(Position pos)
{
    return Poison::Make(pos, fCompiler.context());
}

std::unique_ptr<Expression> Parser::expressionOrPoison(Position pos, std::unique_ptr<Expression> expr)
{
    if (!expr) {
        // If no expression was passed in, create a poison expression.
        expr = this->poison(pos);
    }
    // If a valid position was passed in, it must match the expression's position.
    SkASSERTF(!pos.valid() || expr->position() == pos, "expected expression position (%d-%d), but received (%d-%d)",
        pos.startOffset(), pos.endOffset(), expr->position().startOffset(), expr->position().endOffset());
    return expr;
}

bool Parser::operatorRight(Parser::AutoDepth &depth, Operator::Kind op, BinaryParseFn rightFn,
    std::unique_ptr<Expression> &expr)
{
    this->nextToken();
    if (!depth.increase()) {
        return false;
    }
    std::unique_ptr<Expression> right = (this->*rightFn)();
    if (!right) {
        return false;
    }
    Position pos = expr->position().rangeThrough(right->position());
    expr = this->expressionOrPoison(pos,
        BinaryExpression::Convert(fCompiler.context(), pos, std::move(expr), op, std::move(right)));
    return true;
}

/* assignmentExpression (COMMA assignmentExpression)* */
std::unique_ptr<Expression> Parser::expression()
{
    AutoDepth depth(this);
    [[maybe_unused]] Token start = this->peek();
    std::unique_ptr<Expression> result = this->assignmentExpression();
    if (!result) {
        return nullptr;
    }
    while (this->peek().fKind == Token::Kind::TK_COMMA) {
        if (!this->operatorRight(depth, Operator::Kind::COMMA, &Parser::assignmentExpression, result)) {
            return nullptr;
        }
    }
    SkASSERTF(result->position().valid(), "Expression %s has invalid position", result->description().c_str());
    SkASSERTF(result->position().startOffset() == this->position(start).startOffset(),
        "Expected %s to start at %d (first token: '%.*s'), but it has range %d-%d\n", result->description().c_str(),
        this->position(start).startOffset(), (int)this->text(start).length(), this->text(start).data(),
        result->position().startOffset(), result->position().endOffset());
    return result;
}

/* ternaryExpression ((EQEQ | STAREQ | SLASHEQ | PERCENTEQ | PLUSEQ | MINUSEQ | SHLEQ | SHREQ |
   BITWISEANDEQ | BITWISEXOREQ | BITWISEOREQ | LOGICALANDEQ | LOGICALXOREQ | LOGICALOREQ)
   assignmentExpression)*
 */
std::unique_ptr<Expression> Parser::assignmentExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->ternaryExpression();
    if (!result) {
        return nullptr;
    }
    for (;;) {
        Operator::Kind op;
        switch (this->peek().fKind) {
            case Token::Kind::TK_EQ:
                op = Operator::Kind::EQ;
                break;
            case Token::Kind::TK_STAREQ:
                op = Operator::Kind::STAREQ;
                break;
            case Token::Kind::TK_SLASHEQ:
                op = Operator::Kind::SLASHEQ;
                break;
            case Token::Kind::TK_PERCENTEQ:
                op = Operator::Kind::PERCENTEQ;
                break;
            case Token::Kind::TK_PLUSEQ:
                op = Operator::Kind::PLUSEQ;
                break;
            case Token::Kind::TK_MINUSEQ:
                op = Operator::Kind::MINUSEQ;
                break;
            case Token::Kind::TK_SHLEQ:
                op = Operator::Kind::SHLEQ;
                break;
            case Token::Kind::TK_SHREQ:
                op = Operator::Kind::SHREQ;
                break;
            case Token::Kind::TK_BITWISEANDEQ:
                op = Operator::Kind::BITWISEANDEQ;
                break;
            case Token::Kind::TK_BITWISEXOREQ:
                op = Operator::Kind::BITWISEXOREQ;
                break;
            case Token::Kind::TK_BITWISEOREQ:
                op = Operator::Kind::BITWISEOREQ;
                break;
            default:
                return result;
        }
        if (!this->operatorRight(depth, op, &Parser::assignmentExpression, result)) {
            return nullptr;
        }
    }
}

/* logicalOrExpression ('?' expression ':' assignmentExpression)? */
std::unique_ptr<Expression> Parser::ternaryExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> base = this->logicalOrExpression();
    if (!base) {
        return nullptr;
    }
    if (!this->checkNext(Token::Kind::TK_QUESTION)) {
        return base;
    }
    if (!depth.increase()) {
        return nullptr;
    }
    std::unique_ptr<Expression> trueExpr = this->expression();
    if (!trueExpr) {
        return nullptr;
    }
    if (!this->expect(Token::Kind::TK_COLON, "':'")) {
        return nullptr;
    }
    std::unique_ptr<Expression> falseExpr = this->assignmentExpression();
    if (!falseExpr) {
        return nullptr;
    }
    Position pos = base->position().rangeThrough(falseExpr->position());
    return this->expressionOrPoison(pos, TernaryExpression::Convert(fCompiler.context(), pos, std::move(base),
        std::move(trueExpr), std::move(falseExpr)));
}

/* logicalXorExpression (LOGICALOR logicalXorExpression)* */
std::unique_ptr<Expression> Parser::logicalOrExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->logicalXorExpression();
    if (!result) {
        return nullptr;
    }
    while (this->peek().fKind == Token::Kind::TK_LOGICALOR) {
        if (!this->operatorRight(depth, Operator::Kind::LOGICALOR, &Parser::logicalXorExpression, result)) {
            return nullptr;
        }
    }
    return result;
}

/* logicalAndExpression (LOGICALXOR logicalAndExpression)* */
std::unique_ptr<Expression> Parser::logicalXorExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->logicalAndExpression();
    if (!result) {
        return nullptr;
    }
    while (this->peek().fKind == Token::Kind::TK_LOGICALXOR) {
        if (!this->operatorRight(depth, Operator::Kind::LOGICALXOR, &Parser::logicalAndExpression, result)) {
            return nullptr;
        }
    }
    return result;
}

/* bitwiseOrExpression (LOGICALAND bitwiseOrExpression)* */
std::unique_ptr<Expression> Parser::logicalAndExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->bitwiseOrExpression();
    if (!result) {
        return nullptr;
    }
    while (this->peek().fKind == Token::Kind::TK_LOGICALAND) {
        if (!this->operatorRight(depth, Operator::Kind::LOGICALAND, &Parser::bitwiseOrExpression, result)) {
            return nullptr;
        }
    }
    return result;
}

/* bitwiseXorExpression (BITWISEOR bitwiseXorExpression)* */
std::unique_ptr<Expression> Parser::bitwiseOrExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->bitwiseXorExpression();
    if (!result) {
        return nullptr;
    }
    while (this->peek().fKind == Token::Kind::TK_BITWISEOR) {
        if (!this->operatorRight(depth, Operator::Kind::BITWISEOR, &Parser::bitwiseXorExpression, result)) {
            return nullptr;
        }
    }
    return result;
}

/* bitwiseAndExpression (BITWISEXOR bitwiseAndExpression)* */
std::unique_ptr<Expression> Parser::bitwiseXorExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->bitwiseAndExpression();
    if (!result) {
        return nullptr;
    }
    while (this->peek().fKind == Token::Kind::TK_BITWISEXOR) {
        if (!this->operatorRight(depth, Operator::Kind::BITWISEXOR, &Parser::bitwiseAndExpression, result)) {
            return nullptr;
        }
    }
    return result;
}

/* equalityExpression (BITWISEAND equalityExpression)* */
std::unique_ptr<Expression> Parser::bitwiseAndExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->equalityExpression();
    if (!result) {
        return nullptr;
    }
    while (this->peek().fKind == Token::Kind::TK_BITWISEAND) {
        if (!this->operatorRight(depth, Operator::Kind::BITWISEAND, &Parser::equalityExpression, result)) {
            return nullptr;
        }
    }
    return result;
}

/* relationalExpression ((EQEQ | NEQ) relationalExpression)* */
std::unique_ptr<Expression> Parser::equalityExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->relationalExpression();
    if (!result) {
        return nullptr;
    }
    for (;;) {
        Operator::Kind op;
        switch (this->peek().fKind) {
            case Token::Kind::TK_EQEQ:
                op = Operator::Kind::EQEQ;
                break;
            case Token::Kind::TK_NEQ:
                op = Operator::Kind::NEQ;
                break;
            default:
                return result;
        }
        if (!this->operatorRight(depth, op, &Parser::relationalExpression, result)) {
            return nullptr;
        }
    }
}

/* shiftExpression ((LT | GT | LTEQ | GTEQ) shiftExpression)* */
std::unique_ptr<Expression> Parser::relationalExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->shiftExpression();
    if (!result) {
        return nullptr;
    }
    for (;;) {
        Operator::Kind op;
        switch (this->peek().fKind) {
            case Token::Kind::TK_LT:
                op = Operator::Kind::LT;
                break;
            case Token::Kind::TK_GT:
                op = Operator::Kind::GT;
                break;
            case Token::Kind::TK_LTEQ:
                op = Operator::Kind::LTEQ;
                break;
            case Token::Kind::TK_GTEQ:
                op = Operator::Kind::GTEQ;
                break;
            default:
                return result;
        }
        if (!this->operatorRight(depth, op, &Parser::shiftExpression, result)) {
            return nullptr;
        }
    }
}

/* additiveExpression ((SHL | SHR) additiveExpression)* */
std::unique_ptr<Expression> Parser::shiftExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->additiveExpression();
    if (!result) {
        return nullptr;
    }
    for (;;) {
        Operator::Kind op;
        switch (this->peek().fKind) {
            case Token::Kind::TK_SHL:
                op = Operator::Kind::SHL;
                break;
            case Token::Kind::TK_SHR:
                op = Operator::Kind::SHR;
                break;
            default:
                return result;
        }
        if (!this->operatorRight(depth, op, &Parser::additiveExpression, result)) {
            return nullptr;
        }
    }
}

/* multiplicativeExpression ((PLUS | MINUS) multiplicativeExpression)* */
std::unique_ptr<Expression> Parser::additiveExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->multiplicativeExpression();
    if (!result) {
        return nullptr;
    }
    for (;;) {
        Operator::Kind op;
        switch (this->peek().fKind) {
            case Token::Kind::TK_PLUS:
                op = Operator::Kind::PLUS;
                break;
            case Token::Kind::TK_MINUS:
                op = Operator::Kind::MINUS;
                break;
            default:
                return result;
        }
        if (!this->operatorRight(depth, op, &Parser::multiplicativeExpression, result)) {
            return nullptr;
        }
    }
}

/* unaryExpression ((STAR | SLASH | PERCENT) unaryExpression)* */
std::unique_ptr<Expression> Parser::multiplicativeExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->unaryExpression();
    if (!result) {
        return nullptr;
    }
    for (;;) {
        Operator::Kind op;
        switch (this->peek().fKind) {
            case Token::Kind::TK_STAR:
                op = Operator::Kind::STAR;
                break;
            case Token::Kind::TK_SLASH:
                op = Operator::Kind::SLASH;
                break;
            case Token::Kind::TK_PERCENT:
                op = Operator::Kind::PERCENT;
                break;
            default:
                return result;
        }
        if (!this->operatorRight(depth, op, &Parser::unaryExpression, result)) {
            return nullptr;
        }
    }
}

/* postfixExpression | (PLUS | MINUS | NOT | PLUSPLUS | MINUSMINUS) unaryExpression */
std::unique_ptr<Expression> Parser::unaryExpression()
{
    AutoDepth depth(this);
    Operator::Kind op;
    Token start = this->peek();
    switch (start.fKind) {
        case Token::Kind::TK_PLUS:
            op = Operator::Kind::PLUS;
            break;
        case Token::Kind::TK_MINUS:
            op = Operator::Kind::MINUS;
            break;
        case Token::Kind::TK_LOGICALNOT:
            op = Operator::Kind::LOGICALNOT;
            break;
        case Token::Kind::TK_BITWISENOT:
            op = Operator::Kind::BITWISENOT;
            break;
        case Token::Kind::TK_PLUSPLUS:
            op = Operator::Kind::PLUSPLUS;
            break;
        case Token::Kind::TK_MINUSMINUS:
            op = Operator::Kind::MINUSMINUS;
            break;
        default:
            return this->postfixExpression();
    }
    this->nextToken();
    if (!depth.increase()) {
        return nullptr;
    }
    std::unique_ptr<Expression> expr = this->unaryExpression();
    if (!expr) {
        return nullptr;
    }
    Position pos = Position::Range(start.fOffset, expr->position().endOffset());
    return this->expressionOrPoison(pos, PrefixExpression::Convert(fCompiler.context(), pos, op, std::move(expr)));
}

/* term suffix* */
std::unique_ptr<Expression> Parser::postfixExpression()
{
    AutoDepth depth(this);
    std::unique_ptr<Expression> result = this->term();
    if (!result) {
        return nullptr;
    }
    for (;;) {
        Token t = this->peek();
        switch (t.fKind) {
            case Token::Kind::TK_FLOAT_LITERAL:
                if (this->text(t)[0] != '.') {
                    return result;
                }
                [[fallthrough]];
            case Token::Kind::TK_LBRACKET:
            case Token::Kind::TK_DOT:
            case Token::Kind::TK_LPAREN:
            case Token::Kind::TK_PLUSPLUS:
            case Token::Kind::TK_MINUSMINUS: {
                if (!depth.increase()) {
                    return nullptr;
                }
                result = this->suffix(std::move(result));
                if (!result) {
                    return nullptr;
                }
                break;
            }
            default:
                return result;
        }
    }
}

std::unique_ptr<Expression> Parser::swizzle(Position pos, std::unique_ptr<Expression> base,
    std::string_view swizzleMask, Position maskPos)
{
    SkASSERT(!swizzleMask.empty());
    if (!base->type().isVector() && !base->type().isScalar()) {
        return this->expressionOrPoison(pos,
            FieldAccess::Convert(fCompiler.context(), pos, std::move(base), swizzleMask));
    }
    return this->expressionOrPoison(pos,
        Swizzle::Convert(fCompiler.context(), pos, maskPos, std::move(base), swizzleMask));
}

std::unique_ptr<Expression> Parser::call(Position pos, std::unique_ptr<Expression> base, ExpressionArray args)
{
    return this->expressionOrPoison(pos,
        SkSL::FunctionCall::Convert(fCompiler.context(), pos, std::move(base), std::move(args)));
}

/* LBRACKET expression? RBRACKET | DOT IDENTIFIER | LPAREN arguments RPAREN |
   PLUSPLUS | MINUSMINUS | COLONCOLON IDENTIFIER | FLOAT_LITERAL [IDENTIFIER] */
std::unique_ptr<Expression> Parser::suffix(std::unique_ptr<Expression> base)
{
    AutoDepth depth(this);
    Token next = this->nextToken();
    if (!depth.increase()) {
        return nullptr;
    }
    switch (next.fKind) {
        case Token::Kind::TK_LBRACKET: {
            if (this->checkNext(Token::Kind::TK_RBRACKET)) {
                this->error(this->rangeFrom(next), "missing index in '[]'");
                return this->poison(this->rangeFrom(base->position()));
            }
            std::unique_ptr<Expression> index = this->expression();
            if (!index) {
                return nullptr;
            }
            this->expect(Token::Kind::TK_RBRACKET, "']' to complete array access expression");

            Position pos = this->rangeFrom(base->position());
            return this->expressionOrPoison(pos,
                IndexExpression::Convert(fCompiler.context(), pos, std::move(base), std::move(index)));
        }
        case Token::Kind::TK_DOT: {
            std::string_view text;
            if (this->identifier(&text)) {
                Position pos = this->rangeFrom(base->position());
                return this->swizzle(pos, std::move(base), text, this->rangeFrom(this->position(next).after()));
            }
            [[fallthrough]];
        }
        case Token::Kind::TK_FLOAT_LITERAL: {
            // Swizzles that start with a constant number, e.g. '.000r', will be tokenized as
            // floating point literals, possibly followed by an identifier. Handle that here.
            std::string_view field = this->text(next);
            SkASSERT(field[0] == '.');
            field.remove_prefix(1);
            // use the next *raw* token so we don't ignore whitespace - we only care about
            // identifiers that directly follow the float
            Position pos = this->rangeFrom(base->position());
            Position start = this->position(next);
            // skip past the "."
            start = Position::Range(start.startOffset() + 1, start.endOffset());
            Position maskPos = this->rangeFrom(start);
            Token id = this->nextRawToken();
            if (id.fKind == Token::Kind::TK_IDENTIFIER) {
                pos = this->rangeFrom(base->position());
                maskPos = this->rangeFrom(start);
                return this->swizzle(pos, std::move(base), std::string(field) + std::string(this->text(id)), maskPos);
            }
            if (field.empty()) {
                this->error(pos, "expected field name or swizzle mask after '.'");
                return this->poison(pos);
            }
            this->pushback(id);
            return this->swizzle(pos, std::move(base), field, maskPos);
        }
        case Token::Kind::TK_LPAREN: {
            ExpressionArray args;
            if (this->peek().fKind != Token::Kind::TK_RPAREN) {
                for (;;) {
                    std::unique_ptr<Expression> expr = this->assignmentExpression();
                    if (!expr) {
                        return nullptr;
                    }
                    args.push_back(std::move(expr));
                    if (!this->checkNext(Token::Kind::TK_COMMA)) {
                        break;
                    }
                }
            }
            this->expect(Token::Kind::TK_RPAREN, "')' to complete function arguments");
            Position pos = this->rangeFrom(base->position());
            return this->call(pos, std::move(base), std::move(args));
        }
        case Token::Kind::TK_PLUSPLUS:
        case Token::Kind::TK_MINUSMINUS: {
            Operator::Kind op =
                (next.fKind == Token::Kind::TK_PLUSPLUS) ? Operator::Kind::PLUSPLUS : Operator::Kind::MINUSMINUS;
            Position pos = this->rangeFrom(base->position());
            return this->expressionOrPoison(pos,
                PostfixExpression::Convert(fCompiler.context(), pos, std::move(base), op));
        }
        default: {
            this->error(next, "expected expression suffix, but found '" + std::string(this->text(next)) + "'");
            return nullptr;
        }
    }
}

/* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | '(' expression ')' */
std::unique_ptr<Expression> Parser::term()
{
    AutoDepth depth(this);
    Token t = this->peek();
    switch (t.fKind) {
        case Token::Kind::TK_IDENTIFIER: {
            std::string_view text;
            if (this->identifier(&text)) {
                Position pos = this->position(t);
                return this->expressionOrPoison(pos,
                    this->symbolTable()->instantiateSymbolRef(fCompiler.context(), text, pos));
            }
            break;
        }
        case Token::Kind::TK_INT_LITERAL: {
            SKSL_INT i;
            if (!this->intLiteral(&i)) {
                i = 0;
            }
            Position pos = this->position(t);
            return this->expressionOrPoison(pos, SkSL::Literal::MakeInt(fCompiler.context(), pos, i));
        }
        case Token::Kind::TK_FLOAT_LITERAL: {
            SKSL_FLOAT f;
            if (!this->floatLiteral(&f)) {
                f = 0.0f;
            }
            Position pos = this->position(t);
            return this->expressionOrPoison(pos, SkSL::Literal::MakeFloat(fCompiler.context(), pos, f));
        }
        case Token::Kind::TK_TRUE_LITERAL: // fall through
        case Token::Kind::TK_FALSE_LITERAL: {
            bool b;
            SkAssertResult(this->boolLiteral(&b));
            Position pos = this->position(t);
            return this->expressionOrPoison(pos, SkSL::Literal::MakeBool(fCompiler.context(), pos, b));
        }
        case Token::Kind::TK_LPAREN: {
            this->nextToken();
            if (!depth.increase()) {
                return nullptr;
            }
            std::unique_ptr<Expression> result = this->expression();
            if (result != nullptr) {
                this->expect(Token::Kind::TK_RPAREN, "')' to complete expression");
                result->setPosition(this->rangeFrom(this->position(t)));
                return result;
            }
            break;
        }
        default:
            this->nextToken();
            this->error(t, "expected expression, but found '" + std::string(this->text(t)) + "'");
            fEncounteredFatalError = true;
            break;
    }
    return nullptr;
}

/* INT_LITERAL */
bool Parser::intLiteral(SKSL_INT *dest)
{
    Token t;
    if (!this->expect(Token::Kind::TK_INT_LITERAL, "integer literal", &t)) {
        return false;
    }
    std::string_view s = this->text(t);
    if (!SkSL::stoi(s, dest)) {
        this->error(t, "integer is too large: " + std::string(s));
        return false;
    }
    return true;
}

/* FLOAT_LITERAL */
bool Parser::floatLiteral(SKSL_FLOAT *dest)
{
    Token t;
    if (!this->expect(Token::Kind::TK_FLOAT_LITERAL, "float literal", &t)) {
        return false;
    }
    std::string_view s = this->text(t);
    if (!SkSL::stod(s, dest)) {
        this->error(t, "floating-point value is too large: " + std::string(s));
        return false;
    }
    return true;
}

/* TRUE_LITERAL | FALSE_LITERAL */
bool Parser::boolLiteral(bool *dest)
{
    Token t = this->nextToken();
    switch (t.fKind) {
        case Token::Kind::TK_TRUE_LITERAL:
            *dest = true;
            return true;
        case Token::Kind::TK_FALSE_LITERAL:
            *dest = false;
            return true;
        default:
            this->error(t, "expected 'true' or 'false', but found '" + std::string(this->text(t)) + "'");
            return false;
    }
}

/* IDENTIFIER */
bool Parser::identifier(std::string_view *dest)
{
    Token t;
    if (this->expect(Token::Kind::TK_IDENTIFIER, "identifier", &t)) {
        *dest = this->text(t);
        return true;
    }
    return false;
}
} // namespace SkSL
